/// The `RelativeStrengthIndex` struct in the YATA library, as documented at <https://docs.rs/yata/latest/yata/indicators/struct.RelativeStrengthIndex.html>, is an implementation of the Relative Strength Index (RSI), a popular momentum oscillator used in technical analysis to evaluate the strength of an asset's price action and to anticipate potential trend reversals.
/// ### Key Points about the RelativeStrengthIndex Struct:
///
/// 1. **Main Value Range:** The main value of the RSI ranges between 0.0 and 1.0, representing the normalized ratio of average gains to average losses over a specified time period. Values closer to 1.0 suggest stronger upward momentum, while values closer to 0.0 indicate stronger downward momentum.
///
/// 2. **Signals:**
///    - **Signal #1 (Enters Over-Zone):** This signal is generated when the main RSI value crosses the upper threshold (overbought zone) upwards, indicating a "full sell" signal, as it implies the asset might be overbought and due for a pullback.
///    - **Signal #2 (Leaves Over-Zone):** Conversely, this signal is generated when the main RSI value crosses the upper threshold downwards after being in the overbought region, suggesting a "full buy" signal as it implies the selling pressure might be easing.
///    - Similarly, the RSI can generate buy/sell signals based on crossing the lower threshold (oversold zone), but the documentation specifically mentions "on enters over-zone" and "on leaves over-zone" for the signal descriptions.
///
/// 3. **Overbought/Oversold Relative Zone:** The default overbought/oversold threshold is set at 0.3, meaning that values above 0.7 (since 1.0 - 0.3 = 0.7) are considered overbought and values below 0.3 are deemed oversold. This is a customizable parameter, allowing traders to adjust sensitivity according to their strategy.
///
/// 4. **Source Type of Values:** By default, the RSI calculation uses the closing prices (`Close`) of the financial instrument. However, this could potentially be configured to use other price types (Open, High, Low) depending on the library's flexibility.
///
/// 5. **Configuration Parameters:**
///    - **Period:** Determines the number of periods (typically days) used to calculate the RSI. The default period is not explicitly stated in the snippet but is generally 14 periods in traditional RSI calculations.
///    - **Moving Average Type:** Although not directly detailed in the snippet, the RSI calculation typically involves a smoothing mechanism, often an Exponential Moving Average (EMA), Simple Moving Average (SMA), or other types. The documentation refers to a `MovingAverageConstructor` type `M`, which suggests flexibility in choosing the type of moving average for internal calculations.
///
/// 6. **Functionality:** The struct allows for dynamic initialization based on a configuration, evaluation over a series of Open-High-Low-Close (OHLC) data, and validation of configurations. It provides methods for getting the indicator's name, validating configurations, dynamically setting parameters, and returning results in the form of `IndicatorResult` structures.
///
/// ### Summary:
///
/// The `RelativeStrengthIndex` struct in YATA provides a flexible and configurable implementation of the RSI indicator, enabling traders to analyze market momentum, identify overbought and oversold conditions, and generate trading signals based on these conditions. With customizable parameters like the period, overbought/oversold thresholds, and potentially the type of moving average used for smoothing, traders can tailor the RSI to suit their specific analytical needs and trading strategies.
///
use crate::db::FUDB;
use crate::model::indicators;
use crate::model::indicators::rsi;
use crate::model::indicators::IndicatorResultWrap;
///
use crate::model::kline::Kline;
use crate::model::FudaRecord;
use anyhow::anyhow;
use anyhow::ensure;
use anyhow::Result;
use surrealdb::opt::PatchOp;
use surrealdb::sql::Id;
use surrealdb::sql::Thing;
use yata::core::Candle;
use yata::core::IndicatorConfig;
use yata::core::IndicatorInstance;
use yata::indicators::RelativeStrengthIndexInstance;

#[derive(Debug)]
pub struct Rsi {
    rsi_instance: RelativeStrengthIndexInstance,
}

impl Rsi {
    pub fn new(conf: &rsi::RSIConfig, init_kline: &Kline) -> Result<Self> {
        let rsi = yata::indicators::RelativeStrengthIndex {
            ma: yata::helpers::MA::SMA(conf.ma),
            zone: conf.zone,
            ..Default::default()
        };
        let candle: Candle = init_kline.into();
        let rsi_instance: RelativeStrengthIndexInstance = rsi.init(&candle)?;
        Ok(Rsi { rsi_instance })
    }

    pub fn next(&mut self, kline: &Kline) -> rsi::RSI {
        Self::compute(&mut self.rsi_instance, kline)
    }

    pub fn repeat(&mut self, kline: &Kline) -> rsi::RSI {
        let mut rsi_instance = self.rsi_instance.clone();
        Self::compute(&mut rsi_instance, kline)
    }

    fn compute(rsi_instance: &mut RelativeStrengthIndexInstance, kline: &Kline) -> rsi::RSI {
        let candle: Candle = kline.into();

        let idr: yata::core::IndicatorResult = rsi_instance.next(&candle);

        let idrw: IndicatorResultWrap = IndicatorResultWrap::from(&idr);

        let value = rsi::RSI {
            signal0: idrw.s0,
            signal1: idrw.s1,
            main: idrw.v0,
            upper: 0.7,
            lower: 0.3,
        };

        return value;
    }
}

/// 更新指定K线数据的RSI,现在只能做全量计算
///
/// @param klines: K线数据
///
/// config: Rsi 设置
///
/// return 更新的记录数
///
pub async fn update_rsi(
    klines: &Vec<Kline>,
    config: Option<indicators::rsi::RSIConfig>
) -> Result<u32> {
    ensure!(klines.len() > 0, "klines is empty");
    let mut updates = 0u32;
    if let Some(fstk) = klines.first() {
        let rsi = match config {
            Some(conf) =>
                yata::indicators::RelativeStrengthIndex {
                    ma: yata::helpers::MA::SMA(conf.ma),
                    zone: conf.zone,
                    ..Default::default()
                },
            None => yata::indicators::RelativeStrengthIndex::default(),
        };

        let mut rsi: yata::indicators::RelativeStrengthIndexInstance = rsi.init(
            &(fstk.open, fstk.high, fstk.low, fstk.close, fstk.volume as f64)
        )?;

        for (i, kline) in klines.iter().enumerate() {
            if let Some(Thing { id: Id::String(kid_str), .. }) = &kline.id {
                let idr: yata::core::IndicatorResult = rsi.next(
                    &(kline.open, kline.high, kline.low, kline.close, kline.volume as f64)
                );

                println!("idr[{i}] close={} ==> {:?}", kline.close, idr);

                let idrw: IndicatorResultWrap = IndicatorResultWrap::from(&idr);

                let rsi = indicators::rsi::RSI {
                    signal0: idrw.s0,
                    signal1: idrw.s1,
                    main: idrw.v0,
                    upper: 0.7,
                    lower: 0.3,
                };

                let updated: Option<FudaRecord> = FUDB.update(("kline", kid_str))
                    .patch(PatchOp::replace("/rsi", rsi)).await
                    .map_err(|err| anyhow!(err))?;

                if let Some(_) = updated {
                    updates += 1;
                }
            }
        }
    }

    Ok(updates)
}

#[cfg(test)]
mod test {
    use crate::db;
    use crate::model::indicators;
    use crate::model::kline::Klt;
    use crate::model::kline::KLINE_COMMON_FIELDS;

    #[tokio::test]
    async fn test_update_rsi() {
        let _ = db::init().await.unwrap();

        let klt_vec = vec![
            Klt::M005,
            Klt::M015,
            Klt::M030,
            Klt::M060,
            Klt::DAY,
            Klt::WEEK,
            Klt::MONTH
        ];
        for klt in &klt_vec {
            let klines = db::kline
                ::select("SZ.KZZ.128136", None, Some(klt), Some("klts ASC"), 0, 10000).await
                .unwrap();
            let result = db::indicators::rsi::update_rsi(&klines, None).await;
            println!("{:?}", result);
            assert_eq!(result.is_ok(), true);
        }

        let result = db::kline::select(
            "SZ.KZZ.128136",
            None,
            Some(&Klt::M005),
            Some("klts ASC"),
            0,
            100
        ).await;
        if let Ok(klines) = &result {
            for (index, kline) in klines.iter().enumerate() {
                if index > 30 {
                    break;
                }
                // println!(
                //     "[{:02}], close:{:.2}=>rsi:{:?}",
                //     index, kline.close, kline.rsi,
                // );
            }
        }
    }
}
// [00], close:158.07=>rsi:Some(RSI { signal0: None, signal1: None, signal2: None, main_v: 0.3083, signal_v: 0.3228 })
// [01], close:157.97=>rsi:Some(RSI { signal0: None, signal1: None, signal2: None, main_v: 0.3272, signal_v: 0.3482 })
// [02], close:157.13=>rsi:Some(RSI { signal0: None, signal1: None, signal2: None, main_v: 0.3328, signal_v: 0.3769 })
// [03], close:158.71=>rsi:Some(RSI { signal0: None, signal1: None, signal2: None, main_v: 0.3846, signal_v: 0.4222 })
// [04], close:156.67=>rsi:Some(RSI { signal0: None, signal1: None, signal2: None, main_v: 0.4132, signal_v: 0.4654 })
// [05], close:159.00=>rsi:Some(RSI { signal0: None, signal1: None, signal2: None, main_v: 0.4688, signal_v: 0.5145 })
// [06], close:158.79=>rsi:Some(RSI { signal0: None, signal1: None, signal2: None, main_v: 0.5143, signal_v: 0.5618 })
// [07], close:158.32=>rsi:Some(RSI { signal0: None, signal1: None, signal2: None, main_v: 0.5603, signal_v: 0.612 })
// [08], close:159.26=>rsi:Some(RSI { signal0: None, signal1: None, signal2: None, main_v: 0.6108, signal_v: 0.6604 })
// [09], close:159.67=>rsi:Some(RSI { signal0: None, signal1: None, signal2: None, main_v: 0.6648, signal_v: 0.6962 })
// [10], close:159.80=>rsi:Some(RSI { signal0: None, signal1: None, signal2: None, main_v: 0.7057, signal_v: 0.7229 })
// [11], close:159.32=>rsi:Some(RSI { signal0: None, signal1: None, signal2: None, main_v: 0.7179, signal_v: 0.7423 })
// [12], close:160.20=>rsi:Some(RSI { signal0: None, signal1: None, signal2: None, main_v: 0.745, signal_v: 0.7648 })
